1 module hip.tween;
2 
3 import core.stdc.math:cos, sin, pow, sqrt;
4 private enum PI = 3.141592653589793;
5 public import hip.timer;
6 
7 private enum c1 = 1.70158f;
8 private enum c2 = c1 * 1.525;
9 private enum c3 = c1+1;
10 private enum c4 = (2.0f * PI)/3.0f;
11 private enum c5 = (2 * PI) / 4.5f;
12 private enum n1 = 7.5625f;
13 private enum d1 = 2.75f;
14 
15 //TODO: Change (x) to (float x) for not generating templates needlessly
16 /**
17 *   Credits to https://easings.net as I don't understand most of those functions
18 */
19 enum HipEasing : float function(float x)
20 {
21     identity        = (x) => x,
22     easeInSine      = (x) => 1 - cos((x*PI)/2),
23     easeOutSine     = (x) => sin((x*PI)/2),
24     easeInOutSine   = (x) => -(cos(PI*x) - 1)/2,
25     easeInQuad      = (x) => x*x,
26     easeOutQuad     = (x) => 1 - (1-x) * (1-x),
27     easeInOutQuad   = (x) => x < 0.5f ? 2 *x*x : 1 - pow(-2 * x + 2, 2)/2,
28     easeInCubic     = (x) => x*x*x,
29     easeOutCubic    = (x) => 1 - pow(1-x, 3),
30     easeInOutCubic  = (x) => x < 0.5 ? 4 * x * x * x : 1 - pow(-2 * x + 2, 3)/2,
31     easeInQuart     = (x) => x*x*x*x,
32     easeOutQuart    = (x) => 1 - pow(1-x, 4),
33     easeInOutQuart  = (x) => x < 0.5 ? 8 * x * x * x * x : 1 - pow(-2 * x + 2, 4)/2,
34     easeInQuint     = (x) => x*x*x*x*x,
35     easeOutQuint    = (x) => 1 - pow(1-x, 5),
36     easeInOutQuint  = (x) => x < 0.5 ? 16 * x * x * x * x * x : 1 - pow(-2 * x + 2, 5)/2,
37     easeInExpo      = (x) => x == 0 ? 0 : pow(2, 10 * x- 10),
38     easeOutExpo     = (x) => x == 1 ? 1 : 1 - pow(2, -10 * x),
39     easeInOutExpo   = (x) => x == 0 ? 0 
40                              : x == 1 ? 1 
41                              : x < 0.5 ? pow(2, 20 * x - 10)/2 
42                              : (2 - pow(2, -20 * x + 10))/2,
43     easeInCirc      = (x) => 1 - sqrt(1 - pow(x, 2)),
44     easeOutCirc     = (x) => sqrt(1 - pow(x - 1, 2)),
45     easeInOutCirc   = (x) => x < 0.5 ? (1 - sqrt(1 - pow(2 * x, 2)))/2
46                              : (sqrt(1 - pow(-2 * x + 2, 2)) + 1) / 2,
47     easeInBack      = (x) => c3 * x * x * x - c1 * x * x,
48     easeOutBack     = (x) => 1 + c3 * pow(x - 1, 3) + c1 * pow(x-1, 2),
49     easeInOutBack   = (x) => x < 0.5 ? (pow(2*x, 2) * ((c2+1) * 2 * x - c2))/2
50                              : (pow(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2)/2,
51     easeInElastic   = (x) => x == 0   ? 0
52                              : x == 1 ? 1 
53                              : -pow(2, 10 * x - 10) * sin((x * 10 - 10.75f) * c4),
54     easeOutElastic  = (x) => x == 0   ? 0
55                              : x == 1 ? 1
56                              : pow(2, -10 * x) * sin((x * 10 - 0.75f) * c4) + 1,
57     easeInOutElastic= (x) => x == 0 ? 0 : x == 1 ? 1 : x < 0.5 
58                              ? -(pow(2, 20 * x - 10) * sin((20 * x - 11.125f) * c5))/2
59                              : (pow(2, -20 * x + 10) * sin((20 * x - 11.125f) * c5))/2 + 1,
60     easeInBounce    = (x) => 1 - HipEasing.easeOutBounce(1 - x),
61     easeOutBounce   = function float(x)
62     {
63         if(x < 1.0f / d1)
64             return n1 * x * x;
65         else if(x < 2.0f / d1)
66             return n1 * (x-= 1.5f / d1) * x + 0.75;
67         else if(x < 2.5f / d1)
68             return n1 * (x-= 2.25f / d1) * x + 0.9375f;
69         else
70             return n1 * (x-= 2.625f / d1) * x + 0.984375f;
71     },
72     easeInOutBounce = (x) => x < 0.5 ? (1 - easeOutBounce(1 - 2 * x))/2
73                              : (1+ easeOutBounce(2 * x - 1))/2
74 }
75 
76 
77 class HipTween : HipTimer, IHipFiniteTask
78 {
79     HipEasing easing = null;
80     protected void[] savedData = null;
81 
82     protected void delegate() onPlay;
83     protected void delegate()[] onFinish;
84 
85     this(float durationSeconds, bool loops = false)
86     {
87         super("Tween", durationSeconds, HipTimerType.progressive, loops);
88         this.easing = null;
89     }
90     HipTween setEasing(HipEasing easing){this.easing = easing; return this;}
91     void setProperties(string name, float durationSeconds, bool loops = false)
92     {
93         super.setProperties(name, durationSeconds, HipTimerType.progressive, loops);
94     }
95     override HipTween play()
96     {
97         super.play();
98         if(onPlay != null)
99             onPlay();
100         return this;
101     }
102     override void stop()
103     {
104         super.stop();
105         foreach(finish; onFinish)
106             finish();
107     }
108 
109     protected void allocSaveData(size_t size)
110     {
111         if(savedData !is null)
112             destroy(savedData);
113         savedData = new void[](size);
114     }
115 
116     private static void checkSizes(size_t lengthValues, size_t lengthTarget)
117     {
118         assert(lengthTarget >= 1, "Target values must have 1 or more values");
119         if(lengthTarget != 1)
120             assert(lengthValues == lengthTarget,
121                 "Must have the same number of pointers to targets if targetValues is greater than 1"
122             );
123     }
124 
125     /**
126     *   This version is more lightweight compiler wise as it is not templated 
127     */
128     static HipTween to (float durationSeconds, float*[] valuesRef, in float[] targetValues ...)
129     {
130         checkSizes(valuesRef.length, targetValues.length);
131         HipTween t = new HipTween(durationSeconds, false);
132         float[] v2 = targetValues.dup;
133         t.allocSaveData(valuesRef.length * float.sizeof);
134 
135         t.onPlay = ()
136         {
137             float[] savedDataConv = cast(float[])t.savedData;
138             foreach(i, v; valuesRef)
139                 savedDataConv[i] = *v;
140             
141             t.addHandler((float prog, uint loops) 
142             {
143                 float multiplier = prog;
144                 if(t.easing != null)
145                     multiplier = t.easing(multiplier);
146                 float initialValue;
147                 float newValue;
148 
149                 foreach(i, value; valuesRef)
150                 {
151                     initialValue = savedDataConv[i];
152                     if(v2.length > 1)
153                         newValue = ((1-multiplier)*initialValue + (v2[i] * multiplier));
154                     else
155                         newValue = ((1-multiplier)*initialValue + (v2[0] * multiplier));
156                     *value = newValue;
157                 }
158             });
159         };
160         return t;
161     }
162 
163     static HipTween to(string[] Props, T, V)(float durationSeconds, T target, V[]  values...)
164     {
165         checkSizes(Props.length, values.length);
166         HipTween t = new HipTween(durationSeconds, false);
167         t.allocSaveData(Props.length * V.sizeof);
168 
169         V[] v2 = values.dup;
170         t.onPlay = ()
171         {
172             V[] savedDataConv = cast(V[])t.savedData;
173             static foreach(i, p; Props)
174             {
175                 savedDataConv[i] = cast(V)__traits(getMember, target, p);
176             }
177                         
178             
179             t.addHandler((float prog, uint loops) 
180             {
181                 float multiplier = prog;
182                 if(t.easing != null)
183                     multiplier = t.easing(multiplier);
184                 V initialValue;
185                 V newValue;
186                 static foreach(i, p; Props)
187                 {
188                     initialValue = savedDataConv[i];
189                     if(v2.length > 1)
190                         newValue = cast(V)((1-multiplier)*initialValue + (v2[i] * multiplier));
191                     else
192                         newValue = cast(V)((1-multiplier)*initialValue + (v2[0] * multiplier));
193                     __traits(getMember, target, p) = newValue;
194                 }
195             });
196         };
197         return t;
198     }
199 
200     static HipTween by(float durationSeconds, float*[] valuesRef, float[] targetValues)
201     {
202         checkSizes(valuesRef.length, targetValues.length);
203         HipTween t = new HipTween(durationSeconds, false);
204         t.allocSaveData(float.sizeof * valuesRef.length);
205         float[] v2 = targetValues.dup;
206 
207         t.onPlay = ()
208         {
209             t.addHandler((float prog, uint loops) 
210             {
211                 float[] savedDataConv = cast(float[])t.savedData;
212                 float multiplier = prog;
213                 if(t.easing != null)
214                     multiplier = t.easing(multiplier);
215                 float temp;
216                 float temp2;
217                 foreach(i, valueRef; valuesRef)
218                 {
219                     temp = savedDataConv[i];
220                     if(v2.length > 1)
221                         temp2 = (v2[i] * multiplier);
222                     else 
223                         temp2 = (v2[0] * multiplier);
224                     
225                     *valueRef+= -temp + temp2;
226                     //Copy the new values for being subtracted next frame
227                     savedDataConv[i] = temp2;
228 
229                 }
230             });
231         };
232 
233         return t;
234     }
235 
236     static HipTween by(string[] Props, T, V)(float durationSeconds, T target, V[]  values...)
237     {
238         checkSizes(Props.length, values.length);
239         HipTween t = new HipTween(durationSeconds, false);
240         t.allocSaveData(Props.length * V.sizeof);
241         V[] v2 = values.dup;
242 
243         t.onPlay = ()
244         {
245             t.addHandler((float prog, uint loops) 
246             {
247                 V[] savedDataConv = cast(V[])t.savedData;
248                 float multiplier = prog;
249                 if(t.easing != null)
250                     multiplier = t.easing(multiplier);
251                 V temp;
252                 V temp2;
253                 static foreach(i, p; Props)
254                 {
255                     temp = savedDataConv[i];
256                     if(v2.length > 1)
257                         temp2 = cast(V)(v2[i] * multiplier);
258                     else
259                         temp2 = cast(V)(v2[0] * multiplier);
260 
261                     __traits(getMember, target, p)+= -temp + temp2;
262                     //Copy the new values for being subtracted next frame
263                     savedDataConv[i] = temp2;
264                 }
265             });
266         };
267 
268         return t;
269     }
270 
271     HipTween addOnFinish(void delegate() onFinish)
272     {
273         this.onFinish~= onFinish;
274         return this;
275     }
276     ~this(){destroy(savedData);}
277 
278 }